#include "Function.hpp"
#include "EquationSolver.hpp"
#include <iostream>
#include <cmath>

const double Pi = acos(-1.0);  // Define constant for Pi

// Class F1: Represents the function f(x) = 1/x - tan(x)
class F1 : public Function {
public:
    double operator() (double x) const {
        return 1.0 / x - tan(x);
    }
};

// Class F2: Represents the function f(x) = 1/x - 2^x
class F2 : public Function {
public:
    double operator() (double x) const {
        return 1.0 / x - pow(2, x);
    }
};

// Class F3: Represents the function f(x) = 2^(-x) + e^x + 2cos(x) - 6
class F3 : public Function {
public:
    double operator() (double x) const {
        return pow(2, -x) + exp(x) + 2 * cos(x) - 6;
    }
};

// Class F4: Represents the rational function f(x) = (x^3 + 4x^2 + 3x + 5) / (2x^3 - 9x^2 + 18x - 2)
class F4 : public Function {
public:
    double operator() (double x) const {
        return (x * x * x + 4 * x * x + 3 * x + 5) / (2 * x * x * x - 9 * x * x + 18 * x - 2);
    }
};

// Function to solve f(x) = 1/x - tan(x) using the Bisection Method
void solve_f1() {
    std::cout << "Solving 1/x - tan(x) on [0, Pi/2]" << std::endl;
    Bisection_Method solver_f1(F1(), 0, Pi / 2);  // Initialize the Bisection Method solver for F1
    double x = solver_f1.solve();  // Solve for the root
    std::cout << "An approximate root of F1 is: " << x << std::endl << std::endl;
}

// Function to solve f(x) = 1/x - 2^x using the Bisection Method
void solve_f2() {
    std::cout << "Solving 1/x - 2^x on [0, 1]" << std::endl;
    Bisection_Method solver_f2(F2(), 0, 1);  // Initialize the Bisection Method solver for F2
    double x = solver_f2.solve();  // Solve for the root
    std::cout << "An approximate root of F2 is: " << x << std::endl << std::endl;
}

// Function to solve f(x) = 2^(-x) + e^x + 2cos(x) - 6 using the Bisection Method
void solve_f3() {
    std::cout << "Solving 2^(-x) + e^x + 2cos(x) - 6 on [1, 3]" << std::endl;
    Bisection_Method solver_f3(F3(), 1, 3);  // Initialize the Bisection Method solver for F3
    double x = solver_f3.solve();  // Solve for the root
    std::cout << "An approximate root of F3 is: " << x << std::endl << std::endl;
}

// Function to solve F4 using the Bisection Method
void solve_f4() {
    std::cout << "Solving rational function on [0, 4]" << std::endl;
    Bisection_Method solver_f4(F4(), 0, 4);  // Initialize the Bisection Method solver for F4
    double x = solver_f4.solve();  // Solve for the root
    std::cout << "An approximate root of F4 is: " << x << std::endl << std::endl;
}

int main() {
    solve_f1();  // Solve F1 using the Bisection Method
    solve_f2();  // Solve F2 using the Bisection Method
    solve_f3();  // Solve F3 using the Bisection Method
    solve_f4();  // Solve F4 using the Bisection Method

    return 0;
}